/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain (a) copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.sentry.ohos.core.util;

import ohos.app.Context;
import ohos.bundle.IBundleManager;
import ohos.rpc.RemoteException;

import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;

import io.sentry.ILogger;
import io.sentry.SentryLevel;
import io.sentry.ohos.core.IBuildInfoProvider;
import io.sentry.util.Objects;

/**
 * RootChecker
 *
 * @since 2021-06-01
 */
@ApiStatus.Internal
public final class RootChecker {
    /**
     * the UTF-8 Charset
     */
    @SuppressWarnings("CharsetObjectCanBeUsed")
    private static final Charset UTF_8 = Charset.forName("UTF-8");
    private final @NotNull Context context;
    private final @NotNull IBuildInfoProvider buildInfoProvider;
    private final @NotNull ILogger logger;
    private final @NotNull String[] rootFiles;
    private final @NotNull String[] rootPackages;
    private final @NotNull Runtime runtime;

    /**
     * RootChecker
     *
     * @param context Context
     * @param buildInfoProvider IBuildInfoProvider
     * @param logger ILogger
     */
    public RootChecker(
        final @NotNull Context context,
        final @NotNull IBuildInfoProvider buildInfoProvider,
        final @NotNull ILogger logger) {
        this(
            context,
            buildInfoProvider,
            logger,
            new String[]{
                "/system/app/Superuser.apk",
                "/sbin/su",
                "/system/bin/su",
                "/system/xbin/su",
                "/data/local/xbin/su",
                "/data/local/bin/su",
                "/system/sd/xbin/su",
                "/system/bin/failsafe/su",
                "/data/local/su",
                "/su/bin/su",
                "/su/bin",
                "/system/xbin/daemonsu"
            },
            new String[]{
                "com.devadvance.rootcloak",
                "com.devadvance.rootcloakplus",
                "com.koushikdutta.superuser",
                "com.thirdparty.superuser",
                "eu.chainfire.supersu", // SuperSU
                "com.noshufou.ohos.su" // superuser
            },
            Runtime.getRuntime());
    }

    public RootChecker(
        final @NotNull Context context,
        final @NotNull IBuildInfoProvider buildInfoProvider,
        final @NotNull ILogger logger,
        final @NotNull String[] rootFiles,
        final @NotNull String[] rootPackages,
        final @NotNull Runtime runtime) {
        this.context = Objects.requireNonNull(context, "The application context is required.");
        this.buildInfoProvider =
            Objects.requireNonNull(buildInfoProvider, "The BuildInfoProvider is required.");
        this.logger = Objects.requireNonNull(logger, "The Logger is required.");
        this.rootFiles = Objects.requireNonNull(rootFiles, "The root Files are required.");
        this.rootPackages = Objects.requireNonNull(rootPackages, "The root packages are required.");
        this.runtime = Objects.requireNonNull(runtime, "The Runtime is required.");
    }

    /**
     * Check if the device is rooted or not
     *
     * @return whether the device is rooted or not
     */
    public boolean isDeviceRooted() {
        return checkTestKeys() || checkRootFiles() || checkSuExist() || checkRootPackages();
    }

    /**
     * checkTestKeys
     *
     * @return whether if it contains test keys or not
     */
    private boolean checkTestKeys() {
        final String buildTags = buildInfoProvider.getBuildTags();
        return buildTags != null && buildTags.contains("test-keys");
    }

    /**
     * Often the rooted device have the following files . This method will check whether the device is
     * having these files or not
     *
     * @return whether if the root files exist or not
     */
    private boolean checkRootFiles() {
        for (final String path : rootFiles) {
            try {
                if (new File(path).exists()) {
                    return true;
                }
            } catch (RuntimeException e) {
                logger.log(
                    SentryLevel.ERROR, e, "Error when trying to check if root file %s exists.", path);
            }
        }
        return false;
    }

    /**
     * this will check if SU(Super User) exist or not
     *
     * @return whether su exists or not
     */
    private boolean checkSuExist() {
        Process process = null;
        final String[] su = {"/system/xbin/which", "su"};

        try {
            process = runtime.exec(su);

            try (final BufferedReader reader =
                     new BufferedReader(new InputStreamReader(process.getInputStream(), UTF_8))) {
                String readStr = reader.readLine();
                return readStr != null;
            }
        } catch (IOException e) {
            logger.log(SentryLevel.DEBUG, "SU isn't found on this Device.");
        } catch (Exception e) {
            logger.log(SentryLevel.DEBUG, "Error when trying to check if SU exists.", e);
        } finally {
            if (process != null) {
                process.destroy();
            }
        }
        return false;
    }

    /**
     * some application hide the root status of the ohos device. This will check for those files
     *
     * @return whether the root packages exist or not
     */
    private boolean checkRootPackages() {
        IBundleManager bundleManager = context.getBundleManager();
        if (bundleManager != null) {
            try {
                for (String pkg : rootPackages) {
                    bundleManager.getBundleInfo(pkg, 0);
                    return true;
                }
            } catch (RemoteException e) {
                e.getMessage();
            }
        }
        return false;
    }
}
